package com.michael.demos.ddd.springboot.domains;

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Value;

import java.time.LocalDateTime;
import java.util.Optional;

/**
 * 类功能描述:
 * <pre>
 *   持有一定数额资金的账户。
 *   一个账户对象包含一个最新帐户活动的窗口。
 *   这个账户的总余额是在第一个活动之前有效的基线平衡的总和窗口和活动值的总和。
 * </pre>
 *
 * @author Michael
 * @version 1.0
 * @date 2021/1/12 11:28
 */
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class Account {

    /** 唯一标识符 */
    @Getter
    private final AccountId id;

    /** 帐户的基准余额。这是活动窗口中第一个活动之前的帐户余额。 */
    @Getter
    private final Money baselineBalance;

    /** 这个账户的最新活动窗口 */
    @Getter
    private final ActivityWindow activityWindow;

    /**
     * 创建一个没有ID的账户实体
     */
    public static Account withoutId(Money baselineBalance, ActivityWindow activityWindow) {
        return new Account(null, baselineBalance, activityWindow);
    }

    /**
     * 创建一个有ID的账户实体
     */
    public static Account withId(
            AccountId accountId,
            Money baselineBalance,
            ActivityWindow activityWindow
    ) {
        return new Account(accountId, baselineBalance, activityWindow);
    }

    public Optional<AccountId> getId() {
        return Optional.ofNullable(this.id);
    }

    /**
     * 通过将活动值添加到基线余额，计算帐户的总余额
     */
    public Money calculateBalance() {
        return Money.add(
                this.baselineBalance,
                this.activityWindow.calculateBalance(this.id)
        );
    }

    /**
     * 从账户中取钱，如果成功，则创建一个负值的新活动。
     *
     * @return 取钱是否成功
     */
    public boolean withdraw(Money money, AccountId targetAccountId) {

        if (!mayWithdraw(money)) {
            return false;
        }

        Activity withdrawal = new Activity(
                this.id,
                this.id,
                targetAccountId,
                LocalDateTime.now(),
                money
        );
        this.activityWindow.addActivity(withdrawal);
        return true;
    }

    private boolean mayWithdraw(Money money) {
        return Money.add(
                this.calculateBalance(),
                money.negate()
        ).isPositiveOrZero();
    }

    /**
     * 存钱到这个账户，如果成功，创建一个正值的新活动
     *
     * @return 存钱是否成功
     */
    public boolean deposit(Money money, AccountId sourceAccountId) {
        Activity deposit = new Activity(
                this.id,
                sourceAccountId,
                this.id,
                LocalDateTime.now(),
                money
        );
        this.activityWindow.addActivity(deposit);
        return true;
    }

    @Value
    public static class AccountId {
        private Long value;
    }
}
